package hello;

import java.io.Serializable;

import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.Transaction;

import persistence.HibernateUtil;
import auction.chapter08.UserCustomizeLoadSQL;
import auction.chapter10.ItemVersion;
import auction.chapter10.ItemVersionWithoutNumberTimestamp;

/**
 * Chapter 10
 * @author Wei Sun
 *
 */
public class HelloWorldTxAndConcurrency {
	
	public static void main(String[] args) throws Exception {
		//testTx();
		//testVersion();
		//testVersionWithoutNumberTimestamp();
		//testLock();
		//testLockNoneVersionedObj();
		testNonTransactionally();
		//testAutocommitTrue();
	}
	
	public static void testLockNoneVersionedObj() throws Exception{
		
		
		Session s2=HibernateUtil.getSessionFactory().openSession();
		Transaction tx2=s2.beginTransaction();
		
		Message2 loadm=(Message2)s2.get(Message2.class, 1L);
		
		tx2.commit();
		s2.close();
		
		Session s3=HibernateUtil.getSessionFactory().openSession();
		Transaction tx3=s3.beginTransaction();
		//Thread.sleep(15000);
		s3.lock(loadm, LockMode.FORCE);
		tx3.commit();
		s3.close();
	}
	
	public static void testNonTransactionally() throws Exception{
		/*
		//read works OK
		Session s2=HibernateUtil.getSessionFactory().openSession();
		Message2 loadm=(Message2)s2.get(Message2.class, 1L);
		out(loadm.getText());
		s2.close();
		*/
		
		//MySQL driver will rollback any pending transaction
		//hibernate.connection.autocommit=false
		//  native: no record
		//  increment: no record
		//hibernate.connection.autocommit=true, record is there
		//  native: have record
		//  increment: have record
		
		Session s3=HibernateUtil.getSessionFactory().openSession();
		Message2 m=new Message2();
		m.setText("iiii");
		Serializable id=s3.save(m);
		out(id);
		s3.flush();//MUST flush as hibernate disabled automatic flushing
		s3.close();
	}
	
	/**
	 * hibernate.connection.autocommit=true with normal begin tx and commmit tx
	 * it works as before
	 * @throws Exception
	 */
	public static void testAutocommitTrue() throws Exception{
		
		HibernateUtil.props();
		
		Session s3=HibernateUtil.getSessionFactory().openSession();
		Transaction t=s3.beginTransaction();
		Message2 m=new Message2();
		m.setText("iiii");
		Serializable id=s3.save(m);
		out(id);
		t.commit();
		s3.close();
	}
	
	public static void testLock() throws Exception{
		
		
		Session s2=HibernateUtil.getSessionFactory().openSession();
		Transaction tx2=s2.beginTransaction();
		
		ItemVersion loadiv=(ItemVersion)s2.get(ItemVersion.class, 1L);
		
		//LockMode.NONE:Default
		//LockMode.READ:do a version check,if obj updated throw StaleObjectStateException
		//LockMode.UPGRADE: select ... from update. other update transaction is waiting.
		//LockMode.UPGRADE_NOWAIT: select ... from update nowait, throw exception if waiting
		//LockMode.FORCE: increase version number
		//LockMode.WRITE: Invalid lock mode for lock()
		
		//s2.lock(loadiv, LockMode.UPGRADE_NOWAIT);
		//Thread.sleep(15000);
		/*
		String title=(String)s2.createQuery("select i.title from ItemVersion i where i.id=:itemid")
									.setParameter("itemid", loadiv.getId())
									.uniqueResult();
		*/
		tx2.commit();
		s2.close();
		
		Session s3=HibernateUtil.getSessionFactory().openSession();
		Transaction tx3=s3.beginTransaction();
		//Thread.sleep(15000);
		s3.lock(loadiv, LockMode.FORCE);
		tx3.commit();
		s3.close();
	}
	
	public static void testVersionWithoutNumberTimestamp() throws Exception{
		
		Session s1=HibernateUtil.getSessionFactory().openSession();
		Transaction tx1=s1.beginTransaction();
		
		ItemVersionWithoutNumberTimestamp iv=new ItemVersionWithoutNumberTimestamp();
		iv.setCode("aa");
		iv.setTitle("ttt");
		//s1.save(iv);
		
		tx1.commit();
		s1.close();
		
		Session s2=HibernateUtil.getSessionFactory().openSession();
		Transaction tx2=s2.beginTransaction();
		
		ItemVersionWithoutNumberTimestamp loadiv=(ItemVersionWithoutNumberTimestamp)s2.load(ItemVersionWithoutNumberTimestamp.class, 1L);
		loadiv.setCode("updated aa");
		
		
		tx2.commit();
		s2.close();
	}
	
	public static void testVersion() throws Exception{
		Session s1=HibernateUtil.getSessionFactory().openSession();
		Transaction tx1=s1.beginTransaction();
		
		ItemVersion iv=new ItemVersion();
		iv.setCode("aa");
		iv.setTitle("ttt");
		//s1.save(iv);
		
		tx1.commit();
		s1.close();
		
		
		Session s2=HibernateUtil.getSessionFactory().openSession();
		Transaction tx2=s2.beginTransaction();
		
		ItemVersion loadiv=(ItemVersion)s2.load(ItemVersion.class, 1L);
		loadiv.setCode("updated aa");
		
		
		tx2.commit();
		s2.close();
	}

	public static void testTx() throws Exception {
		Session s1 = null;
		Transaction tx1 = null;

		try {
			s1 = HibernateUtil.getSessionFactory().openSession();
			tx1 = s1.beginTransaction();
			doSth(s1);
			
			tx1.commit();
		} catch (RuntimeException e1) {
			out(e1);
			try {
				tx1.rollback();//call roll back one each implementation of Transaction
			} catch (RuntimeException e2) {
				out("Counldn't roll back transaction" + e2);
			}
			throw e1;//throw this exception
		} finally {
			s1.close();
		}
	}
	
	private static void doSth(Session s) throws Exception{
		UserCustomizeLoadSQL u=new UserCustomizeLoadSQL();
		u.setName("xx");
		u.setAddress("addr");
		
		s.save(u);//trigger flush immediately
		out("After save but Before commit...");
		//there is no record in DB before transaction is committed
		Thread.currentThread().sleep(10*1000);
		String nu=null;
		//!!! let it throw NPE
		nu.length();
	}

	public static void getParent(Class clazz) {
		Class parent = clazz.getSuperclass();
		if (parent != null) {
			System.out.println("###" + parent.getName());
			getParent(parent);
		}
	}

	static void out(Object obj) {
		System.out.println("--->" + obj);
	}
}
